﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using DatabaseLayer;
using System.Threading;
using System.Threading.Tasks;

namespace ControlLayer
{
    public class RouteCtr : IRouteCtr
    {
        static TablesDataContext db = new TablesDataContext();

        static object lockobject = new object();

        public List<Flightroute> GetAllRoutesWhere(int travellers, DateTime travelDate)
        {
            IQueryable<Flightroute> flightRouteQue =
                from flightRoute in db.Flightroutes
                where flightRoute.availableSeats >= travellers && travelDate.CompareTo(flightRoute.timeOfDep) <= 0
                select flightRoute;

            return flightRouteQue.ToList();
        }

        public Flightroute GetRoute(int routeId)
        {
            IQueryable<Flightroute> flightRouteQue =
                from flightRoute in db.Flightroutes
                where flightRoute.routeId == routeId
                select flightRoute;
            return flightRouteQue.ToList()[0];
        }

        public List<Airport> GetAllAirports()
        {
            IQueryable<Airport> airQue =
                from airport in db.Airports
                select airport;

            return airQue.ToList();
        }

        public List<Flightroute> GetAllRoutes()
        {
            IQueryable<Flightroute> flightRouteQue =
                from flightRoute in db.Flightroutes
                select flightRoute;

            return flightRouteQue.ToList();
        }

        /// <summary>
        /// http://visualgo.net/sssp.html
        /// http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
        /// </summary>
        public List<Flightroute> GetCheapestRoute(string start, string goal, int travellers, DateTime travelDate)
        {
            #region Initialization
            List<Flightroute> allroutes = new List<Flightroute>();
            List<Port> priorityQue = new List<Port>();
            Port currentPort;
            int maxFlights = 3;

            allroutes = GetAllRoutesWhere(travellers, travelDate);

            //Create the Priorityque along with the Ports which contains the flightroutes
            foreach (var item in allroutes)
            {
                if (!priorityQue.Exists(x => x.name.Equals(item.departure)))
                {
                    Port p = new Port();
                    p.name = item.departure;
                    p.flights.Add(item);
                    priorityQue.Add(p);
                }
                else
                {
                    priorityQue.Find(x => x.name.Equals(item.departure)).flights.Add(item);
                }
            }

            //Add to priorityQue Ports that does not have flightroutes departing from them
            foreach (var item in allroutes)
            {
                 if (!priorityQue.Exists(x => x.name.Equals(item.destination)))
                {
                    Port p = new Port();
                    p.name = item.destination;
                    priorityQue.Add(p);
                }
            }

            //Set the starting point
            currentPort = priorityQue.Find(x => x.name.Equals(start));
            currentPort.isVisited = true;
            currentPort.calculatedValue = 0;

            #endregion

            #region Main Loop
            //While it is not true for all that they are visited  AND  the complete path does not excede 3 flightroutes
            while (!priorityQue.TrueForAll(x => x.isVisited) && currentPort.flightRecord.Count < maxFlights + 1)
            {
                //Calculate currentPort's Neighbor values
                //If lower than seen before, assign new value and the accumulated flightpath + the path to the neighbor
                foreach (var item in currentPort.flights)
                {
                    var neighborPort = priorityQue.Find(x => x.name.Equals(item.destination));

                    if (currentPort.calculatedValue + (double)item.price < neighborPort.calculatedValue)
                    {
                        neighborPort.calculatedValue = (double)item.price;
                        neighborPort.flightRecord.Clear(); //whoops, lille detalje der manglede i det afleverede*
                        neighborPort.flightRecord.AddRange(currentPort.flightRecord);
                        neighborPort.flightRecord.Add(item);
                    }
                }

                //search the priorityQue for the port with the lowest value (that is not visited) and assign the new currentPort
                double comparison = Double.PositiveInfinity;
                int newPortIndex = 0;

                foreach (var item in priorityQue)
                {
                    if (item.calculatedValue < comparison && !item.isVisited)
                    {
                        newPortIndex = priorityQue.IndexOf(item);
                    }
                }

                currentPort = priorityQue[newPortIndex];
                currentPort.isVisited = true;
            }

            #endregion

            var goalPort = priorityQue.Find(x => x.name.Equals(goal));

            if (goalPort.flightRecord.Count > 3 | goalPort.flightRecord.Count == 0 | goalPort.calculatedValue == Double.PositiveInfinity)
                return null;
            else
                return goalPort.flightRecord;
        }

        public TicketReservation ReserveTickets(int[] routeIds, int travellers)
        {
            TicketReservation tick = new TicketReservation();
            Thread t = new Thread(() => tick.ThreadCode(routeIds, travellers));
            t.Start();

            return tick;
        }

        public void BuyTicket(TicketReservation ticket)
        {
            ticket.IsBought = true;
        }

        public static void UpdateSeats(int[] routeIds, int travellers)
        {
            lock (lockobject)
            {
                foreach (var item in routeIds)
                {
                    IQueryable<Flightroute> frQue =
                            from flightRoute in db.Flightroutes
                            where flightRoute.routeId == item
                            select flightRoute;

                    frQue.ToList()[0].availableSeats -= travellers;

                    db.SubmitChanges();
                }
            }
        }
    }

    //A class used by a thread in ReserveTicket
    public class TicketReservation
    {
        public bool IsBought { get; set; }

        public int WaitTimeInSeconds { get; set; }

        public TicketReservation()
        {
            IsBought = false;
            WaitTimeInSeconds = 60;
        }

        public void ThreadCode(int[] routeIds, int travellers)
        {

            RouteCtr.UpdateSeats(routeIds, travellers);

            int index = 0;
            while (!IsBought && index <= WaitTimeInSeconds)
            {
                Thread.Sleep(1000);
                index++;
            }

            if (!IsBought)
            {
                int tempint = travellers;
                travellers = tempint - (tempint * 2);
                RouteCtr.UpdateSeats(routeIds, travellers);
            }
        }
    }

    //Used by the algorithm
    public class Port
    {
        public string name { get; set; }

        public List<DatabaseLayer.Flightroute> flights { get; set; }

        public double calculatedValue { get; set; }

        public bool isVisited { get; set; }

        public List<DatabaseLayer.Flightroute> flightRecord { get; set; }

        public Port()
        {
            flights = new List<DatabaseLayer.Flightroute>();
            calculatedValue = Double.PositiveInfinity;
            isVisited = false;
            flightRecord = new List<DatabaseLayer.Flightroute>();
        }
    }
}
